﻿// Copyright (c) Inossem. All rights reserved.
// Licensed under the LGPL-3.0 license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DWF.Studio.ProcessExecutor.Core.Engine
{
    /// <summary>
    /// Custom synchronization context implementation using BlockingCollection.
    /// </summary>
    public sealed class QueueSynchronizationContext : SynchronizationContext ,IDisposable
    {
        private struct ContinuationInformation
        {
            public SendOrPostCallback Continuation;
            public object State;
        }

        /// <summary>
        /// Continuation queue.
        /// </summary>
        private readonly BlockingCollection<ContinuationInformation> _excuteQueue = new BlockingCollection<ContinuationInformation>();

        /// <summary>
        /// This synchronization context bound thread id.
        /// </summary>
        private readonly int _targetThreadId = Thread.CurrentThread.ManagedThreadId;

        /// <summary>
        /// Number of recursive posts.
        /// </summary>
        private int _recursiveCount = 0;

        private bool _isDisposed;

        /// <summary>
        /// Constructor.
        /// </summary>
        public QueueSynchronizationContext()
        {
        }

        /// <summary>
        /// Send continuation into synchronization context.
        /// </summary>
        /// <param name="continuation">Continuation callback delegate.</param>
        /// <param name="state">Continuation argument.</param>
        public override void Send(SendOrPostCallback continuation, object state)
        {
            this.Post(continuation, state);
        }

        /// <summary>
        /// Post continuation into synchronization context.
        /// </summary>
        /// <param name="continuation">Continuation callback delegate.</param>
        /// <param name="state">Continuation argument.</param>
        public override void Post(SendOrPostCallback continuation, object state)
        {
            // If current thread id is target thread id:
            var currentThreadId = Thread.CurrentThread.ManagedThreadId;
            if (currentThreadId == _targetThreadId)
            {
                // HACK: If current thread is already target thread, invoke continuation directly.
                //   But if continuation has invokeing Post/Send recursive, cause stack overflow.
                //   We can fix this problem by simple solution: Continuation invoke every post into queue,
                //   but performance will be lost.
                //   This counter uses post for scattering (each 50 times).
                if (_recursiveCount < 50)
                {
                    _recursiveCount++;

                    // Invoke continuation on current thread is better performance.
                    continuation(state);

                    _recursiveCount--;
                    return;
                }
            }

            // Add continuation information into queue.
            _excuteQueue.Add(new ContinuationInformation { Continuation = continuation, State = state });
        }

        /// <summary>
        /// Execute message queue.
        /// </summary>
        public void Run()
        {
            this.Run(null);
        }

        /// <summary>
        /// Execute message queue.
        /// </summary>
        /// <param name="task">Completion awaiting task</param>
        public void Run(Task task)
        {
            // Run only target thread.
            var currentThreadId = Thread.CurrentThread.ManagedThreadId;
            if (currentThreadId != _targetThreadId)
            {
                throw new InvalidOperationException();
            }

            // Schedule task completion for abort queue consumer.
            task?.ContinueWith(_ => _excuteQueue.CompleteAdding());

            // Run queue consumer.
            ContinuationInformation infoItem;
            while (_excuteQueue.TryTake(out infoItem, Timeout.Infinite))
                infoItem.Continuation(infoItem.State);
        }

        public void Complete()
        {
            _excuteQueue.CompleteAdding();
        }

        private void Dispose(bool disposing)
        {
            if (!this._isDisposed)
            {
                if (disposing)
                {
                    _excuteQueue.Dispose();
                }

                this._isDisposed = true;
            }
        }

        ~QueueSynchronizationContext()
        {
            this.Dispose(false);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}
